#!/usr/bin/env python
# -*- coding: utf-8 -*-

# refer to `https://bitbucket.org/akorn/wheezy.captcha`

import random
import string
import os.path
from io import BytesIO

from PIL import Image
from PIL import ImageFilter
from PIL.ImageDraw import Draw
from PIL.ImageFont import truetype


class Bezier:
    def __init__(self):
        self.tsequence = tuple([t / 20.0 for t in range(21)])
        self.beziers = {}

    def pascal_row(self, n):
        """ Returns n-th row of Pascal's triangle
        """
        result = [1]
        x, numerator = 1, n
        for denominator in range(1, n // 2 + 1):
            x *= numerator
            x /= denominator
            result.append(x)
            numerator -= 1
        if n & 1 == 0:
            result.extend(reversed(result[:-1]))
        else:
            result.extend(reversed(result))
        return result

    def make_bezier(self, n):
        """ Bezier curves:
            http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Generalization
        """
        try:
            return self.beziers[n]
        except KeyError:
            combinations = self.pascal_row(n - 1)
            result = []
            for t in self.tsequence:
                tpowers = (t ** i for i in range(n))
                upowers = ((1 - t) ** i for i in range(n - 1, -1, -1))
                coefs = [c * a * b for c, a, b in zip(combinations,
                                                      tpowers, upowers)]
                result.append(coefs)
            self.beziers[n] = result
            return result


class Captcha(object):
    def __init__(self):
        self._bezier = Bezier()
        self._dir = os.path.dirname(__file__)
        # self._captcha_path = os.path.join(self._dir, '..', 'static', 'captcha')

    @staticmethod
    def instance():
        if not hasattr(Captcha, "_instance"):
            Captcha._instance = Captcha()
        return Captcha._instance

    def initialize(self, width=200, height=75, color=None, text=None, fonts=None):
        # self.image = Image.new('RGB', (width, height), (255, 255, 255))
        self._text = text if text else random.sample(string.ascii_uppercase + string.ascii_uppercase + '3456789', 4)
        self.fonts = fonts if fonts else \
            [os.path.join(self._dir, 'fonts', font) for font in ['Arial.ttf', 'Georgia.ttf', 'actionj.ttf']]
        self.width = width
        self.height = height
        self._color = color if color else self.random_color(0, 200, random.randint(220, 255))

    @staticmethod
    def random_color(start, end, opacity=None):
        red = random.randint(start, end)
        green = random.randint(start, end)
        blue = random.randint(start, end)
        if opacity is None:
            return red, green, blue
        return red, green, blue, opacity

    # draw image

    def background(self, image):
        Draw(image).rectangle([(0, 0), image.size], fill=self.random_color(238, 255))
        return image

    @staticmethod
    def smooth(image):
        return image.filter(ImageFilter.SMOOTH)

    def curve(self, image, width=4, number=6, color=None):
        dx, height = image.size
        dx /= number
        path = [(dx * i, random.randint(0, height))
                for i in range(1, number)]
        bcoefs = self._bezier.make_bezier(number - 1)
        points = []
        for coefs in bcoefs:
            points.append(tuple(sum([coef * p for coef, p in zip(coefs, ps)])
                                for ps in zip(*path)))
        Draw(image).line(points, fill=color if color else self._color, width=width)
        return image

    def noise(self, image, number=50, level=2, color=None):
        width, height = image.size
        dx = width / 10
        width -= dx
        dy = height / 10
        height -= dy
        draw = Draw(image)
        for i in range(number):
            x = int(random.uniform(dx, width))
            y = int(random.uniform(dy, height))
            draw.line(((x, y), (x + level, y)), fill=color if color else self._color, width=level)
        return image

    def text(self, image, fonts, font_sizes=None, drawings=None, squeeze_factor=0.75, color=None):
        color = color if color else self._color
        fonts = tuple([truetype(name, size)
                       for name in fonts
                       for size in font_sizes or (65, 70, 75)])
        draw = Draw(image)
        char_images = []
        for c in self._text:
            font = random.choice(fonts)
            c_width, c_height = draw.textsize(c, font=font)
            char_image = Image.new('RGB', (c_width, c_height), (0, 0, 0))
            char_draw = Draw(char_image)
            char_draw.text((0, 0), c, font=font, fill=color)
            char_image = char_image.crop(char_image.getbbox())
            for drawing in drawings:
                d = getattr(self, drawing)
                char_image = d(char_image)
            char_images.append(char_image)
        width, height = image.size
        offset = int((width - sum(int(i.size[0] * squeeze_factor)
                                  for i in char_images[:-1]) -
                      char_images[-1].size[0]) / 2)
        for char_image in char_images:
            c_width, c_height = char_image.size
            mask = char_image.convert('L').point(lambda i: i * 1.97)
            image.paste(char_image,
                        (offset, int((height - c_height) / 2)),
                        mask)
            offset += int(c_width * squeeze_factor)
        return image

    # draw text
    @staticmethod
    def warp(image, dx_factor=0.27, dy_factor=0.21):
        width, height = image.size
        dx = width * dx_factor
        dy = height * dy_factor
        x1 = int(random.uniform(-dx, dx))
        y1 = int(random.uniform(-dy, dy))
        x2 = int(random.uniform(-dx, dx))
        y2 = int(random.uniform(-dy, dy))
        image2 = Image.new('RGB',
                           (width + abs(x1) + abs(x2),
                            height + abs(y1) + abs(y2)))
        image2.paste(image, (abs(x1), abs(y1)))
        width2, height2 = image2.size
        return image2.transform(
            (width, height), Image.QUAD,
            (x1, y1,
             -x1, height2 - y2,
             width2 + x2, height2 + y2,
             width2 - x2, -y1))

    @staticmethod
    def offset(image, dx_factor=0.1, dy_factor=0.2):
        width, height = image.size
        dx = int(random.random() * width * dx_factor)
        dy = int(random.random() * height * dy_factor)
        image2 = Image.new('RGB', (width + dx, height + dy))
        image2.paste(image, (dx, dy))
        return image2

    @staticmethod
    def rotate(image, angle=25):
        return image.rotate(
            random.uniform(-angle, angle), Image.BILINEAR, expand=1)

    def captcha(self, path=None, fmt='JPEG'):
        """Create a captcha.

        Args:
            path: save path, default None.
            fmt: image format, PNG / JPEG.
        Returns:
            A tuple, (name, text, StringIO.value).
            For example:
                ('fXZJN4AFxHGoU5mIlcsdOypa', 'JGW9', '\x89PNG\r\n\x1a\n\x00\x00\x00\r...')

        """
        image = Image.new('RGB', (self.width, self.height), (255, 255, 255))
        image = self.background(image)
        image = self.text(image, self.fonts, drawings=['warp', 'rotate', 'offset'])
        image = self.curve(image)
        image = self.noise(image)
        image = self.smooth(image)
        name = "".join(random.sample(string.ascii_lowercase + string.ascii_uppercase + '3456789', 24))
        text = "".join(self._text)
        out = BytesIO()
        image.save(out, format=fmt)
        if path:
            image.save(os.path.join(path, name), fmt)
        return name, text, out.getvalue()

    def generate_captcha(self):
        self.initialize()
        return self.captcha("")

captcha = Captcha.instance()

if __name__ == '__main__':
    print(captcha.generate_captcha())
    # ('B37vhHquEiG4jZAgDKUOtcSf', 'K4HY', b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xdb\x00C\x00\x08\x06\x06\x07\x06\x05\x08\x07\x07\x07\t\t\x08\n\x0c\x14\r\x0c\x0b\x0b\x0c\x19\x12\x13\x0f\x14\x1d\x1a\x1f\x1e\x1d\x1a\x1c\x1c $.\' ",#\x1c\x1c(7),01444\x1f\'9=82<.342\xff\xdb\x00C\x01\t\t\t\x0c\x0b\x0c\x18\r\r\x182!\x1c!22222222222222222222222222222222222222222222222222\xff\xc0\x00\x11\x08\x00K\x00\xc8\x03\x01"\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\xff\xc4\x00\xb5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05\x04\x04\x00\x00\x01}\x01\x02\x03\x00\x04\x11\x05\x12!1A\x06\x13Qa\x07"q\x142\x81\x91\xa1\x08#B\xb1\xc1\x15R\xd1\xf0$3br\x82\t\n\x16\x17\x18\x19\x1a%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xff\xc4\x00\x1f\x01\x00\x03\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\xff\xc4\x00\xb5\x11\x00\x02\x01\x02\x04\x04\x03\x04\x07\x05\x04\x04\x00\x01\x02w\x00\x01\x02\x03\x11\x04\x05!1\x06\x12AQ\x07aq\x13"2\x81\x08\x14B\x91\xa1\xb1\xc1\t#3R\xf0\x15br\xd1\n\x16$4\xe1%\xf1\x17\x18\x19\x1a&\'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\xf7Z(\xa2\xb3\x10QE!\xce\x0e:\xf6\xa0\x05\xa2\xbc\xa8\xf8\xfbR\xb6\xf8\x924\xddbE\xb0\xb1C\xb3\xcbQ\xb9X\x9e\x84\xb7\xa1\xff\x00=\xeb\xd5\x01\x0c\xa0\x83\x90y\x15N-\x1a\xd4\xa5*v\xe6\xea-\x15\x99\xa9h\xff\x00\xda7v\x97\x1fm\xb9\x83\xec\xed\xbbdO\x85\x93\xd9\xbdEi\xd23iX*\x1b\xab\xbb{\x1bv\xb8\xba\x99!\x85~\xf3\xb9\xc0\x15SO\xd6\xecu;\xab\xabkY\x19\xa5\xb5m\xb2\x82\x84`\xfe=j\xdd\xd5\xa5\xbd\xf5\xbb[\xddB\x93B\xdfy\x1cd\x1a=Gk;H|3G<),N\xaf\x1b\x8c\xab)\xc8#\xd6\x9fL\x86\x18\xe0\x85"\x89\x15#A\x85U\x18\x00zS\xe8$(\xa2\x8a@\x14QE\x00\x14QE\x00\x14QE\x00\x15Z\xe7Q\xb3\xb3\x9a(\xaenb\x8aI\x8e#Wl\x17>\x83\xd6\x9b>\xa3ko/\x97$\x807\xa5$\xb6V\x1a\x8b\xc3q4\x11N\xd1\x1d\xd1;.J\x9fQ\xe9L\xa4\x97R\xe5\x14\xd5\x91\x1c\x90\xae\xacGP\x0eqN\xa4HQE\x14\x00QE\x14\x00U1qq30\x85\x14\x05m\xb9cW+:\x02\xf0\xea\xd2\xc4~\xe4\x83p\xa64p\x1f\x10<56\xbd\x18U\x8e1\xa9\xc2wE(\x18\xde?\xbb\xfe\x7f\xadO\xf0\xff\x00\xc7q\xcdf4Mi\xde-N\xdb\xe4\xfd\xefW\x03\xfa\x8f\xf3\xde\xba\xbf\x11\xc2\xc3\xc9\xb9A\xf7\x0f&\xb8_\x19xRo\x11l\xd648\xb6\xeap\x85/\xb4\xed\xdf\x8e\xff\x00Q\xfe{V\x89\xa6\xac\xce\xdar\x8dH*s\xdb\xa3\xec\xff\x00\xc8\xf4\xfb\x89%\x96\xc6V\xb1h\xdar\xa7\xcb\xdf\xf7wv\xcf\xb5e\xc5\xad\xb6\x99\r\x8d\xb6\xbd$k\xa8]1U\x10!(N\x7fO\xc6\xbc\xcf\xc3_\x12g\xb1\xd4\x12\xc7]\x89\xad\xe6V\xf2\xdd\xb1\x80q\xc7#\xb1\xff\x00<W\xa7\xe8\xda\x8d\xce\xaf$\xd2\\i\xf1\xc7l\xa4\x1bi\xc3\x87\xf3W\xd4zT\xb8\xb8\xeeeR\x8c\xa9i5\xa1\xb0\xb1\xa2\x12U\x15I\xea@\xc6i\xd5\x99\r\xee\xa2\xfa\xdc\xd6\xb2i\xdb,Qr\x97[\xf3\xbc\xfac\xb5M\xa9\x94\x92\xce[c+\xc6\xd2\xa1P\xd1\x9c0\xf7\x1560k]B\xefQ\x8e\xd9\xc4j\xa6I\x0f\xf0\x8au\xa5\xe3O#G${\x1dy=\xc5bi:i\xd2\xac\xc3Iq,\xef\x9cF\xf3\xb6Y\xb3\xdc\x9a\xd0\x0c\xf6\xcaR\xd8,\x931\xcb\xb1\xf5\xa7a\xb4\xb6F\xb18\x04\xd7)\xa7\xf8\x83S\xfe\xd4\xb9\x82\xfe(\x0c\x06M\xb0\x18\xb3\x9c\x7f\xb5Sx\x8e\xfbW\xb1\xd2n\x05\x9ck5\xd3\xc6DaN6\x9f_\xc2\xb0\xfc)\r\xe4\x8dj5\t<\xcb\x9f\xbd!\xc69\xf4\xa6\x96\x974\x84\x17#\x93=\x00\x1c\x80k6\x1f\xed\x8f\xed\xb9\xbc\xef\xb3\x7ffm\xfd\xd6\xdc\xf9\x9b\xbd\xfbb\xb4\xea\x1b\xab\x98\xac\xede\xb9\x98\x91\x14JY\x88\x19\xc0\x15&)\xf4&\xaev\xe3\xc4O\x1d\xf1\x81Qq\xbb\x03\xb94\xeb\xddV\xf3R\xd0c\xbf\xf0\xeb\xc0\xc1\x9b%\xaeT\x81\xb4u\xc0\xeb\x9a\xe7|>\xad\'\x88\xc3]\x8d\xd2\x1c\x95\xf4\xaaK\xb9\xaci\xe8\xdc\x8e\xae\xdf^\x89\xe6\xf2\xa6C\x1b\x13\x8ekF\xe6\xe1m\xed\xdac\xc8\x03\x8fz\xc4\xf1\r\x88\x01n\xa3\\\x11\xf7\xb1R\xdb\xc85X\xed\xe1^#\x8c\x02\xff\x00\xe1J\xc4\xb8\xab&\x88/\xf4\xc9n\xf4k\x89c\xb9\xfb5\xcc\xc3p\x98\xae\xed\x83\xe9Y\x16\x9a.\xa3}o\x1c\xb0\xeaS\xac\xd6\x831\xe1\xb6\xa4\xaf\xfe\xd0\xf4\xf6\xab\x9a\xd5\x91\xbc\xf1\x05\xb9\x82\xee\xe4l]\x8d\n\xbf\xee\xcf\xd4z\xd7So\n\xdb\xc0\x91(\xc0Q\x8aw\xb2/\x9d\xc6*\xc6?\x86\xb4F\xd3!\x9a\xe6\xea8\xd7P\xbam\xd7\x06&%\t\xcf\x18\xcfJ\xdd\xac\xc9\xac\xb5\x17\xd6\xe1\xba\x8fQ\xd9b\x8b\x87\xb5\xd9\x9d\xe7\xd7=\xabN\xa5\x99\xcd\xdd\xdd\xb0\xa2\x8a)\x10\x14QE\x00\x15\xcf\\\xe9me\xe2\x01\xab\x8b\xbb\x87Y>F\x81\x9b\xf7k\xee\x05^\xd4\xbf\xb6>\xd7i\xfd\x9d\xf6o\xb3\xee\xff\x00H\xf3s\xbbo\xfb>\xf5r\xf1\x03\xdaH\x08\xce\x06E=\x8bM\xc7\xe6-\xc4\twl\xd1\xb7\xddaQi\xf6Kcl#\x1c\xb7Rh\xb1\xbc\x8e\xe2\xde?\x9dC\xe3\x05s\xcdK%\xccQL\xb1;mf\xe9\x9e\xf4\x13\xae\xc7\x07\xe3/\x0fi\x9a\xe6\xa8\xabs\x02\x97U\xda\x1d8aXCA\xf1\xb7\x82\xa5/\xa1Lu\r0\x02\xcb\x04\x87%G\xa6=~\x9dk\xd1\xaf\xb4\xc5[\xa5\xbeE/\xb4\xe5\x93\xd7\xe9H|E\n\xea\xd6\xf6\x02\xdar\xb2\xae|\xfd\xbf\xbbS\xe8O\xadZ\x93\xb5\x8e\xa8W\x92\x8a\x8aW]\x99\xc5\xd8|[\x10K\xf6\x7f\x10i76R\xae\x038S\x81\xeaH\xec*\xc1\xf1\xd6\x93uv\xf76\xfa\x8c-\x18l\r\xe7o\x1fC]\xed\xd6\x9fe}\x13Guk\x0c\xc8\xddC\xa09\xaf8\xd7<\x07\xe1\x8d?Z\x8eF\xd2\xee\xee>\xdc\xc5V8\x7f\xd5\xc4}O\xa54\xe2\xfa\x0e\x12\xa17\xf0\xb4\xfe\xff\x00\xcc\xe9a\xf1\x97\x875\x08\x84w\x17\xf6\xd1\xb7l\xb8\xa9l\xf5\xef\x0e\xd9\xefo\xed\xbbG\xee3(\xe2\xb9\x18\xbe\x0eh3K\xb4\\\xdd|\xbf|\x86\x18\x07\xd0T\x0f\xf0\xcb\xc3\xbav\xa4\x85\x92\xea\xea\x15\xe1\x95\x9b\x82\x7f\n-\x0e\xe3\xe4\xc3\xbd\x14\x9f\xdct\x1a\xdf\x8e|;\x05\x9b9\xd4\xa2yf\xe0\x04\xf9\xb6\x8f\xc3\xa5sv\xde6\xd0C\x16\x1a\x9f\x94\xc3\xa1\xday\xa3]\xf8u\xa3}\x8b\x16\xb6\xff\x00e\x9a^Q\xb2N\xd1\xf4\xae\x9b\xc2\x1e\x08\xd1-tuY\xf4\xf8.\x1c\x9ed\x912I\xa7\xee\xa4_\xee!N\xea\xef\xee9\x95\xf8\xac\xb0\xb3\x08c\xbb\x9d\x01\xc0q\x1eCTk\xf1\xa2\xf0\x02&\xd0\x19\xd7\xeaF\x7fJ\xf5kk\r6\xd5\x05\xb5\xbd\xb5\xb2*\xf4\x8dTq\xf8T\xb2XZJ\x85\x1e\xda"\xa7\xb6\xc1S\xcd\x1e\xc6~\xda\x87\xfc\xfb\xfcO#?\x18\xc4\xf1\x1bw\xd0\xe4\x82&\x18,\x878\xfc1Qi\xff\x00\x10tu\xbdF\x9dn-\x88\xe5dt\xe0\x1fz\xef5\xed2\xc2\xd0G\xe4\xdb\xc6\xac\xddx\xac\xcb+=.\xfe\xedt\xcb\xed\x11\xee\x04\xcaqq\xe5|\x88?\xde\xec\x7f\xfa\xd5W\x8d\xb64R\xa2\xe3u\x07\xf7\x9d&\x91\xaf\xe9~"\xb0>M\xd42\x120\xc8\x18dg\xda\xa6+\x0e\x89`\xe4r\xccx\xf75\xc2^\xfc+\xd3\xbf\xb5\xd8i7\x97\x16\x12m\x0e\x85\x1b!H\xac=W\xfe\x12\xff\x00\x04\xc8\x0e\xa1;j\x9aal\x87\'$\x7fQ\xfc\xa9r\xa7\xb33Ti\xcd\xda\x9c\xbeL\xf5}\x1a\xd3lf\xeeQ\x99e\xe7\x9e\xc2\xb5\xab\x9e\xf0\x8f\x8at\xef\x13ik-\x9c\x98\x920\x16H\x9b\x86S\xf4\xab\x9a\x86\xa3}\r\xed\xacV\x16+w\x13\xb6\'q \x1eH\xf5\xc7z\x86\x9d\xceyF\\\xcd=\xc94\xfdn\xc7S\xba\xba\xb6\xb5\x91\x9a[V\xdb((F\x0f\xe3\xd6\xb4i\xab\x1a!%QT\x9e\xa4\x0cf\x9dH\x87k\xe8\x14QE!\x05\x14Q@\x05\x04d`\xd1E\x00sV\xfa\x1d\x9ax\xa4\xdeI\x13\x99\xd1\x7ft\xfb\x8e\x00\xef\xc7J\xd4\xd6m~\xd1f]?\xd6G\xf3\x02)\xfa\x85\xb4\xf2bki6J\xa3\xf3\xac+\xed:\xe2\xf6kig\xbb\xbe\x8c\xc2s\xb2\x1e\x15\xfd\x9b\xd4Unj\x9f3M\xb2\xee\x91\xab\x97O&\xe7\xb7\x01\x8f\xf5\xa8|E\xa4i\xf7\x90#\\\r\xc8\xae\x1f\xcb\x0eWq\xfc)\xb66.\x97\xc4yL\xd08\xc1,*=W\xc2\xf7\x17\x97\xf6\xb3\xc7{:\xac\r\x90\xaa\xd8\x07\xd9\xbdE>\xa3VS\xbav:\x1b+\x88\xee-\x11\xe3\xce\x00\xc6\x0fj\xce}F7\xbbh\xe5\x05$\x1f*n\xe8=\xe9\xb0Zj\xf6\x9a\xac)\t\xb6:i\x19\x98\xb6|\xcd\xde\xdd\xb1L\x8eX5\xfb\x9b\xabw\xb2\xb9\xb6kf\xda%\x910$\xf7S\xdcR\xb1<\xab~\x86\x81+gj\xa9\t\xdc\xf2\x1c\x03\xea}j\xd4p\xa2\xc6\x15\x94\x13\xd4\xe4w\xaa\xf6ZjZ(\xcb\xb4\x8c:\x16\xedWi\x10\xcaw\x9am\xbd\xee\x0c\xaar\x06\x01\x155\xad\xb2Z\xc0\xb1&v\x8fZ\x9a\xb3\xb5\x8d\'\xfb^\xde8~\xd9sk\xb1\xc3\xee\xb7m\xa4\xfb\x1fj\x06\x9d\xf4o@\x87D\xb1\x83X\x9bU\x8e6\x17s.\xd7}\xe7\x04}:V\x8d".\xd4U\xcep1\x93K@\x9bos\x97\xf11\x1fi\x8f\x9ev\xf4\xad+\x1bE\xb8\xd0>\xce\xb2\xbc~j\x10d\x8c\xe1\x97=\xc7\xbdbk\xae\xd2jl0x\xe0V\xae\x93q\xa8%\xe7\xd8\xa4\xd3|\xab\x14\x8c2\\\xef\xce\xf6\xf4\xdb\xda\xab\xa1\xb3O\x91\x0f\xd2\xf4\xdf\xec\xb9b\xb6\xfbD\xd7\x1bc?\xbc\x99\xb2\xe7\x9e\xe6\xa8\xf8\xd2\xdak\xad5"\x82a\x0c\xac\xdc9]\xd8\xfc+;N\xff\x00\x84\xaa=kQ\x97R\xe2\xd0I\x8bS\x80p\x99\xf6\xfe\xb5\xafsl\xf7\xcf\x1e\x04\xcc\xc0\x8c\xb3\x8c\n6w\x0f\x86i\xb7s\x8d\x87\xe1\xee\xa9\xa3\xeb\xdav\xab\xa0^\xacbL\x0b\xb0\xc3\x0b\xeev\xfa\x1fN\xdf\xcb\xd0\xb4\xed\x12\xcbK\xb9\xb9\xb9\xb6\x8d\x96k\xa6\xdd1,N\xe3\xfd)\xfa\x94\xf7\x96zc\xcbci\xf6\xbb\x85\x03l;\xb6\xee\xfcj\xc5\xa4\x93Mi\x14\x97\x10\xf932\x82\xf1\xe7;O\xa6hroqT\xab9\xab\xc9\xf9\x13QE\x15\x06!E\x14P\x01E\x14P\x01E\x14P\x01E\x14P\x01E\x14P\x01E\x14P\x01E\x14P\x01E\x14P\x01E\x14P\x05;\xbd:\x0b\xcc3\xa8\x0e:0\xaa\xf6\xf6:\x8cZ\xcc\xb72j>e\x8b T\xb5\xd9\x8d\x87\xd7=\xebR\x8aw)I\xa5`\xa2\x8a)\x12\x14QE\x00\x14QE\x00\x14QE\x00\x7f\xff\xd9')
    # 第一个值 text 随机的字符串，没用
    # 第二个值 code 随机4位验证码
    # 第三个值 image 图片数据 bytes类型
    # src = "http://127.0.0.1:8000/image_codes/29e28586-00df-40ad-9709-d8f158740e14/"